<template>
  <div class="universalEditor" :class="{ formStatus, fullScreen: general.state.fullscreen }">
    <div class="toolbar">
      <a-space class="left">
        <a-typography-text type="secondary" :strong="true" class="logo">
          UEditor
        </a-typography-text>
        <a-button size="small" @click="handleSaveContent">
          <template #icon>
            <DownloadOutlined />
          </template>
        </a-button>
      </a-space>
      <a-space class="right">
        <template v-if="language === UEditorLanguage.Markdown">
          <a-button size="small" @click="setUploaderModalVisibal(true)">
            <template #icon>
              <CloudUploadOutlined />
            </template>
          </a-button>
          <a-button size="small" @click="setPreview(!isPreview)">
            <template #icon>
              <template v-if="isPreview">
                <EyeInvisibleOutlined />
              </template>
              <template v-else>
                <EyeOutlined />
              </template>
            </template>
          </a-button>
        </template>
        <a-select
          size="small"
          v-model:value="language"
          :options="[
            { label: 'Markdown', value: 'markdown' },
            { label: 'Json', value: 'json' },
          ]"
          @change="setLanguage"
          class="language"
        ></a-select>
        <a-button size="small" @click="general.setFullscreen(!general.state.fullscreen)">
          <template #icon>
            <template v-if="general.state.fullscreen">
              <FullscreenExitOutlined />
            </template>
            <template v-else>
              <FullscreenOutlined />
            </template>
          </template>
        </a-button>
      </a-space>
    </div>
    <div class="container">
      <div
        id="container"
        ref="containerRef"
        class="editor"
        :class="{ placeholder: !value }"
        :placeholder="placeholder"
      ></div>
      <div v-if="isPreview" :class="{ preview: isPreview }">
        <div class="markdown" v-html="markdownToHTML(value || '')"></div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch, watchEffect, onMounted } from 'vue'
import type { PropType } from 'vue'
import {
  DownloadOutlined,
  CloudUploadOutlined,
  EyeInvisibleOutlined,
  EyeOutlined,
  FullscreenExitOutlined,
  FullscreenOutlined,
} from '@ant-design/icons-vue'
import { editor, KeyMod, KeyCode } from './monaco'
import { general } from '@/state/general'
import { saveFile } from '@/services/file'
import { markdownToHTML } from '@/transformers/markdown'
import { timestampToYMD } from '@/transformers/date'

enum UEditorLanguage {
  Markdown = 'markdown',
  Json = 'json',
}
const fileExtMap = new Map([
  [UEditorLanguage.Markdown, 'md'],
  [UEditorLanguage.Json, 'json'],
])

const TOOLBAR_HEIGHT = 48
const SINGLE_LINE_HEIGHT = 24
const MIN_ROWS = 34
const MAX_ROWS = 40

const props = defineProps({
  value: {
    type: String,
  },
  placeholder: {
    type: String,
    default: '请输入内容...',
  },
  disabled: {
    type: Boolean,
  },
  minRows: {
    type: Number,
  },
  maxRows: {
    type: Number,
  },
  cacheID: {
    type: String,
  },
  // 是否禁用顶部工具栏
  disabledToolbar: {
    type: Boolean,
    // default: true
  },
  formStatus: {
    type: Boolean,
  },
  language: {
    type: Object as PropType<UEditorLanguage>,
  },
})

const emits = defineEmits(['update:value'])

// const propValue = ref<string>("");
// const cacheID = props.cacheID;
const containerRef = ref()
let ueditor: editor.IStandaloneCodeEditor
const isPreview = ref<boolean>(false)
const setPreview = (bool: boolean) => {
  isPreview.value = bool
}
const language = ref<UEditorLanguage>(UEditorLanguage.Markdown)
const setLanguage = (_language: UEditorLanguage) => {
  language.value = _language
}

const isVisibleUploaderModal = ref<boolean>(false)
const setUploaderModalVisibal = (bool: boolean) => {
  isVisibleUploaderModal.value = bool
}

const handleSaveContent = () => {
  const time = timestampToYMD(Date.now())
  const fileExt = fileExtMap.get(language.value)
  const fileName = `${props.cacheID}-${time}.${fileExt}`
  saveFile(props.value || '', fileName)
}

const createEditor = () => {
  ueditor = editor.create(containerRef.value!, {
    value: props.value,
    language: language.value,
    theme: 'vs-dark',
    tabSize: 2,
    fontSize: 14,
    lineHeight: SINGLE_LINE_HEIGHT,
    smoothScrolling: true,
    // readOnly: Boolean(props.disabled),
    minimap: {
      enabled: true,
    },
    // 文件夹
    folding: true,
    // 禁用右键菜单
    contextmenu: false,
    // 底部不留空
    scrollBeyondLastLine: false,
    // 根据已有单词自动提示
    wordBasedSuggestions: true,
    // 回车命中选中词
    acceptSuggestionOnEnter: 'on',
    scrollbar: {
      // 滚动事件可冒泡至外层
      alwaysConsumeMouseWheel: false,
    },
  })
  // Command + S = save content
  ueditor.addCommand(KeyMod.CtrlCmd | KeyCode.KeyS, handleSaveContent)
  // ESC = exit fullscreen
  ueditor.addCommand(KeyCode.Escape, () => general.setFullscreen(false))
  return ueditor
}

const handleResizeWidth = () => {
  const widthRatio = isPreview.value ? 0.5 : 1
  const layoutInfo = ueditor?.getLayoutInfo()!
  ueditor?.layout({
    width: general.state.fullscreen
      ? window.innerWidth * widthRatio
      : containerRef.value.clientWidth * widthRatio,
    height: layoutInfo.height,
  })
}

const handleResizeHeight = () => {
  if (!ueditor) {
    return false
  }

  const layoutInfo = ueditor?.getLayoutInfo()!
  let targetHeight: number = 0
  if (general.state.fullscreen) {
    targetHeight = window.innerHeight - TOOLBAR_HEIGHT
  } else {
    // 非全屏，则计算高度
    const maxHeight = (props.maxRows ?? MAX_ROWS) * SINGLE_LINE_HEIGHT
    const minHeight = (props.minRows ?? MIN_ROWS) * SINGLE_LINE_HEIGHT
    const contentHeight = ueditor?.getContentHeight()!
    const lineCount = ueditor?.getModel()?.getLineCount()!
    if (contentHeight) {
      if (contentHeight > maxHeight) {
        targetHeight = maxHeight
      } else {
        const linesHeight = lineCount * SINGLE_LINE_HEIGHT
        if (linesHeight < minHeight) {
          targetHeight = minHeight
        } else {
          targetHeight = linesHeight
        }
      }
    }

    if (layoutInfo.height !== targetHeight) {
      ueditor.layout({
        width: layoutInfo.width,
        height: targetHeight,
      })
    }
  }
}

watch(
  () => props.value,
  () => {
    if (props.value !== ueditor.getValue()) {
      ueditor.setValue(props.value || '')
    }
  }
)

watchEffect(() => {
  const model = ueditor?.getModel()
  if (model && language.value) {
    editor.setModelLanguage(model, language.value)
  }
})

watch(
  () => isPreview.value,
  () => {
    handleResizeWidth()
  }
)

watch(
  () => [general.state.fullscreen, props.maxRows, props.minRows],
  () => {
    handleResizeHeight()
    handleResizeWidth()
  }
)

onMounted(() => {
  ueditor = createEditor()
  // content height change
  const sizeDisposer = ueditor.onDidContentSizeChange(handleResizeHeight)
  // editor value change
  const modelDisposer = ueditor.onDidChangeModelContent(() => {
    const newValue = ueditor!.getValue()
    // if (!props.disabledCacheDraft) {
    //     setUEditorCache(cacheID, newValue)
    // }
    if (newValue !== props.value) {
      emits('update:value', newValue)
    }
  })

  return () => {
    sizeDisposer?.dispose()
    modelDisposer?.dispose()
    ueditor?.dispose?.()
  }
})
</script>
<style lang="less" scoped>
@toolbar-height: 3rem;

.universalEditor {
  width: 100%;
  border: 1px solid #434343;
  border-radius: @radius;

  &.fullScreen {
    width: 100vw;
    height: 100vh;
    margin: 0;
    padding: 0;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    .container {
      height: calc(100vh - @toolbar-height);
    }
  }

  .toolbar {
    height: @toolbar-height;
    padding: 0 @gap;
    border-bottom: 1px solid @border;
    background-color: @body-bg;
    display: flex;
    justify-content: space-between;
    align-items: center;

    .logo {
      user-select: none;
    }

    .language {
      width: 120px;
    }
  }

  .container {
    width: 100%;
    display: flex;
    position: relative;
    overflow: hidden;
    background-color: @module-bg;

    .editor {
      flex: 1;
      position: relative;
      &.placeholder {
        &::before {
          position: absolute;
          top: 0;
          left: 70px;
          z-index: 1;
          content: attr(placeholder);
          color: @text-disabled;
          font-style: italic;
        }
      }
    }

    .preview {
      position: absolute;
      left: 50%;
      top: 0;
      width: 50%;
      height: 100%;
      padding: 2rem;
      overflow-y: scroll;

      .markdown {
        a {
          color: @primary;
          font-weight: bold;
          margin: 0 0.1em;

          &:hover {
            text-decoration: underline !important;
          }
        }

        img {
          max-width: 80%;
          margin: 0 auto;
          display: block;
          text-align: center;
          border-radius: 2px;
          border: 0.5rem solid @border;
        }

        code {
          padding: 0.3em 0.5em;
          margin: 0 0.5em;
          border-radius: 2px;
          background-color: @border;
        }

        p {
          line-height: 2.2em;
          text-indent: 2em;
          margin-bottom: 1em;
        }

        h1,
        h2,
        h3,
        h4,
        h5,
        h6 {
          margin: 1.5rem 0;
          padding-left: 0;
          line-height: 1.8em;
          font-weight: 700;
          text-indent: 0;
        }

        blockquote {
          margin: 1.2em 0;
          text-indent: 0em;
          background-color: @border;
          border-left: 0.5em solid #aaa;
          padding: 0.5em 1em;
          padding-left: 1.5em;

          p {
            text-indent: 0em;

            &:last-child {
              margin-bottom: 0;
            }
          }
        }

        ul {
          list-style-type: square;
        }

        ul,
        ol {
          > li {
            line-height: 1.8em;
            padding: 0.5em 1em;

            &:hover {
              background-color: @module-bg;
            }

            > ul {
              &:last-child {
                margin-bottom: 0;
              }
            }
          }
        }

        pre {
          margin-bottom: 1em;

          > code {
            margin: 0;
            padding: 1em;
            line-height: 1.6em;
            background-color: rgba(0, 0, 0, 0.8);
            position: relative;
            display: block;
            color: rgba(255, 255, 255, 0.87);
          }
        }
      }
    }
  }
}
</style>
